﻿using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Xpo;
using DevExpress.Xpo;
using DevExpress.Xpo.DB;
using DevExpress.Xpo.DB.Helpers;
using Nito.AsyncEx;
using SqlSugar;
using System.Data;
using System.Globalization;

namespace EasyXaf.BulkOperations;

public static class ObjectSpaceExtensions
{
    private static SqlSugar.DbType? GetDbType(string providerKey)
    {
        return providerKey switch
        {
            "MySql" => SqlSugar.DbType.MySql,
            "MSSqlServer" => SqlSugar.DbType.SqlServer,
            "SQLite" => SqlSugar.DbType.Sqlite,
            "Oracle" => SqlSugar.DbType.Oracle,
            "Postgres" => SqlSugar.DbType.PostgreSQL,
            "MSAccess" => SqlSugar.DbType.Access,
            _ => null
        };
    }

    private static SqlSugarClient GetSqlSugarClient(string connectionString)
    {
        var connectionStringParser = new ConnectionStringParser(connectionString);

        var providerKey = connectionStringParser.GetPartByName("XpoProvider");
        if (providerKey != null && providerKey.Length == 0)
        {
            providerKey = null;
        }

        connectionStringParser.RemovePartByName("XpoDataStorePool");
        var plainConnectionString = connectionStringParser.GetConnectionString();

        if (providerKey != null)
        {
            connectionStringParser.RemovePartByName("XpoProvider");
            plainConnectionString = connectionStringParser.GetConnectionString();
        }
        else if (connectionStringParser.GetPartByName("Initial Catalog").Length != 0 && connectionStringParser.GetPartByName("Provider").Length == 0)
        {
            providerKey = "MSSqlServer";
        }
        else
        {
            var name = connectionStringParser.GetPartByName("Provider").ToLower(CultureInfo.InvariantCulture);
            if (name.Contains("microsoft.ace.oledb", StringComparison.CurrentCulture) || name.Contains("microsoft.jet.oledb", StringComparison.CurrentCulture))
            {
                providerKey = "MSAccess";
            }
        }

        if (!string.IsNullOrWhiteSpace(providerKey))
        {
            var dbType = GetDbType(providerKey);
            if (dbType.HasValue)
            {
                return new SqlSugarClient(new ConnectionConfig
                {
                    DbType = dbType.Value,
                    ConnectionString = plainConnectionString
                });
            }
        }

        return null;
    }

    public static T CloneObject<T>(this XPObjectSpace objectSpace, T obj)
        where T : class
    {
        return new ObjectCloneHelper(objectSpace.Session).Clone(obj);
    }

    public static void BulkInsert(this XPObjectSpace objectSpace, string connectionString, Type[] insertedTypes, Action<XPObjectSpace> insertAction)
    {
        if (string.IsNullOrWhiteSpace(connectionString))
        {
            throw new ArgumentNullException(nameof(connectionString));
        }

        if (insertedTypes == null)
        {
            throw new ArgumentNullException(nameof(insertedTypes));
        }

        if (!insertedTypes.Any())
        {
            throw new ArgumentException("数组不能为空", nameof(insertedTypes));
        }

        if (insertAction == null)
        {
            throw new ArgumentNullException(nameof(insertAction));
        }

        var dictionary = objectSpace.Session.Dictionary;
        var dbTables = dictionary.GetDataStoreSchema(insertedTypes.First());

        using var dataset = new DataSet();

        var dataSetDataStore = new DataSetDataStore(dataset, AutoCreateOption.DatabaseAndSchema);
        dataSetDataStore.UpdateSchema(dbTables);

        var objectTypeTable = dataSetDataStore.Data.Tables[nameof(XPObjectType)];
        foreach (var objectType in objectSpace.GetObjects<XPObjectType>())
        {
            var row = objectTypeTable.NewRow();
            row[nameof(XPObjectType.Oid)] = objectType.Oid;
            row[nameof(XPObjectType.TypeName)] = objectType.TypeName;
            row[nameof(XPObjectType.AssemblyName)] = objectType.AssemblyName;
            objectTypeTable.Rows.Add(row);
        }

        using (var session = new UnitOfWork(new SimpleDataLayer(dataSetDataStore)))
        {
            using var os = new XPObjectSpace(objectSpace.TypesInfo, objectSpace.XpoTypeInfoSource, () => session);
            insertAction(os);
            os.CommitChanges();
        }

        using var sqlSugarClient = GetSqlSugarClient(connectionString);
        foreach (var objectType in insertedTypes)
        {
            var tableName = dictionary.GetClassInfo(objectType).TableName;
            var table = dataset.Tables[tableName];
            AsyncContext.Run(async () =>
            {
                await sqlSugarClient.Fastest<DataTable>().AS(tableName).BulkCopyAsync(table);
            });
        }
    }

    public static void BulkInsert(this XPObjectSpace objectSpace, XafApplication application, Type[] insertedTypes, Action<XPObjectSpace> insertAction)
    {
        if (application == null)
        {
            throw new ArgumentNullException(nameof(application));
        }

        if (insertedTypes == null)
        {
            throw new ArgumentNullException(nameof(insertedTypes));
        }

        if (!insertedTypes.Any())
        {
            throw new ArgumentException("数组不能为空", nameof(insertedTypes));
        }

        if (insertAction == null)
        {
            throw new ArgumentNullException(nameof(insertAction));
        }

        var objectSpaceProvider = application.ObjectSpaceProviderContainer.GetObjectSpaceProvider(insertedTypes.First());
        if (objectSpaceProvider != null)
        {
            BulkInsert(objectSpace, objectSpaceProvider.ConnectionString, insertedTypes, insertAction);
        }
    }

    public static void BulkInsert<TEntity>(this XPObjectSpace objectSpace, string connectionString, Action<XPObjectSpace> insertAction)
    {
        BulkInsert(objectSpace, connectionString, new Type[] { typeof(TEntity) }, insertAction);
    }

    public static void BulkInsert<TEntity>(this XPObjectSpace objectSpace, XafApplication application, Action<XPObjectSpace> insertAction)
    {
        BulkInsert(objectSpace, application, new Type[] { typeof(TEntity) }, insertAction);
    }
}
